﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using JTLeigh.Commerce.Spencer.Core.Common.Entities;
using JTLeigh.Commerce.Spencer.Core.Common.UnitTests.Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace JTLeigh.Commerce.Spencer.Core.Business.UnitTests
{

	/// <summary>
	/// Unit tests for the <see cref="RelationsServiceTools"/> class.
	/// </summary>
	[TestClass]
	public class RelationsServiceToolsFixture
	{

		#region Methods

		#region GetAssociationsForCategories

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.GetAssociationsForCategories"/> 
		/// calls the specified <see cref="GetCategoryAssociations{TAssociation}"/> and
		/// <see cref="GetCategoryDisassociations{TDisassociation}"/> delegates for each specified 
        /// category identifier, merges the results, and returns the merged results.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This test doesn't actually ensure the associations and disassociations are merged or 
		/// returned.  We should extend this test when we have more time.
		/// </para>
		/// </remarks>
		[TestMethod]
		public void TestGetAssociationsForCategories()
		{
			List<string> categoryIds;
			int currentAssociation;
			int currentDisassociation;

			categoryIds = new List<string>
			{
				"ABC",
				"DEF",
				"GHI",
				"JKL"
			};
			currentAssociation = 0;
			currentDisassociation = 0;

			RelationsServiceTools.GetAssociationsForCategories<TestAssociation, TestDisassociation>(
				delegate(string categoryId)
				{
					Assert.AreEqual(currentDisassociation, currentAssociation); // NOTE: Associations should be retrieved first.
					Assert.AreEqual(categoryIds[currentAssociation], categoryId);
					currentAssociation++;
					return new Collection<TestAssociation>();
				},
				delegate(string categoryId)
				{
					Assert.AreNotEqual(currentAssociation, currentDisassociation); // NOTE: Disassociations should be retrieved second.
					Assert.AreEqual(categoryIds[currentDisassociation], categoryId);
					currentDisassociation++;
					return new Collection<TestDisassociation>();
				},
				categoryIds,
				new DateTime(2008, 06, 01));

			Assert.AreEqual(categoryIds.Count, currentAssociation);
			Assert.AreEqual(categoryIds.Count, currentDisassociation);
		}

		#endregion // GetAssociationsForCategories

		#region MergeAssociationsAndDisassociations

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociationsAndDisassociations"/> merges 
		/// all source associations and disassociations from the source collections into the 
		/// destination associations collection using the merge rules defined in 
		/// <see cref="RelationsServiceTools.MergeAssociation"/> and
		/// <see cref="RelationsServiceTools.MergeDisassociation"/>.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationsAndDisassociations()
		{
			Collection<TestAssociation> sourceAssociations;
			TestAssociation sourceAssociation1;
			TestAssociation sourceAssociation2;
			TestAssociation sourceAssociation3;
			Collection<TestDisassociation> sourceDisassociations;
			TestDisassociation sourceDisassociation1;
			TestDisassociation sourceDisassociation2;
			Dictionary<Guid, AssociationBase> destinationAssociations;

			sourceAssociations = new Collection<TestAssociation>();
			sourceAssociation1 = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation1);
			sourceAssociation2 = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation2);
			sourceAssociation3 = new TestAssociation
			{
				// NOTE: This association will have no effect on sourceAssociation1, as it starts after our effective date.
				AssociatedItemId = sourceAssociation1.AssociatedItemId,
				StartDate = sourceAssociation1.StartDate.AddDays(1),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation3);

			sourceDisassociations = new Collection<TestDisassociation>();
			sourceDisassociation1 = new TestDisassociation
			{
				// NOTE: This disassociation will filter out sourceAssociation2.
				DisassociatedItemId = sourceAssociation2.AssociatedItemId,
				StartDate = new DateTime(2008, 05, 01),
				FinishDate = new DateTime(2008, 07, 01),
			};
			sourceDisassociations.Add(sourceDisassociation1);
			sourceDisassociation2 = new TestDisassociation
			{
				// NOTE: This disassociation will have no effect on sourceAssociation1/sourceAssociation3, as it starts after our effective date.
				DisassociatedItemId = sourceAssociation3.AssociatedItemId,
				StartDate = new DateTime(2008, 07, 01),
				FinishDate = new DateTime(2008, 08, 01),
			};
			sourceDisassociations.Add(sourceDisassociation2);

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			RelationsServiceTools.MergeAssociationsAndDisassociations<TestAssociation, TestDisassociation, AssociationBase>(sourceAssociations, sourceDisassociations, destinationAssociations, new DateTime(2008, 06, 01));

			Assert.AreEqual(1, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(sourceAssociation1.AssociatedItemId));
			Assert.AreSame(sourceAssociation1, destinationAssociations[sourceAssociation1.AssociatedItemId]);
		}

		#endregion // MergeAssociationsAndDisassociations

		#region MergeAssociations

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociations"/> merges all associations 
		/// from the source collection into the destination collection using the merge rules 
		/// defined in <see cref="RelationsServiceTools.MergeAssociation"/>.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociations()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			Dictionary<Guid, TestAssociation> sourceAssociations;
			TestAssociation sourceAssociation1;
			TestAssociation sourceAssociation2;
			TestAssociation sourceAssociation3;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			sourceAssociations = new Dictionary<Guid, TestAssociation>();

			sourceAssociation1 = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation1.AssociatedItemId, sourceAssociation1);

			RelationsServiceTools.MergeAssociations<TestAssociation, AssociationBase>(sourceAssociations, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(1, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(sourceAssociation1.AssociatedItemId));
			Assert.AreSame(sourceAssociation1, destinationAssociations[sourceAssociation1.AssociatedItemId]);

			// merge a second time, with one of the associations having the same id as 

			sourceAssociations = new Dictionary<Guid, TestAssociation>();

			sourceAssociation2 = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation2.AssociatedItemId, sourceAssociation2);
			sourceAssociation3 = new TestAssociation
			{
				AssociatedItemId = sourceAssociation1.AssociatedItemId,
				StartDate = sourceAssociation1.StartDate.AddDays(1),
				FinishDate = null,
			};
			sourceAssociations.Add(sourceAssociation3.AssociatedItemId, sourceAssociation3);

			RelationsServiceTools.MergeAssociations<TestAssociation, AssociationBase>(sourceAssociations, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(2, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(sourceAssociation2.AssociatedItemId));
			Assert.AreSame(sourceAssociation2, destinationAssociations[sourceAssociation2.AssociatedItemId]);
			Assert.IsTrue(destinationAssociations.ContainsKey(sourceAssociation3.AssociatedItemId));
			Assert.AreSame(sourceAssociation3, destinationAssociations[sourceAssociation3.AssociatedItemId]);
		}

		#endregion // MergeAssociations

		#region MergeAssociation

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociation"/> filters out associations
		/// that have not started and does not add them to the destination association 
		/// collection.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationAssociationHasNotStarted()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			TestAssociation sourceAssociation;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			sourceAssociation = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 04),
				FinishDate = new DateTime(2008, 06, 05),
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(sourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(0, destinationAssociations.Count);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociation"/> filters out associations
		/// that have already finished and does not add them to the destination association 
		/// collection.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationAssociationHasFinished()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			TestAssociation sourceAssociation;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			sourceAssociation = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = new DateTime(2008, 06, 02),
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(sourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(0, destinationAssociations.Count);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociation"/> adds the source 
		/// association to the destination associations collection when there is not already an
		/// association for the source association's item in the collection.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationItemDoesNotExist()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			TestAssociation sourceAssociation;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();
			sourceAssociation = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2006, 01, 01),
				FinishDate = null,
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(sourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(1, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(sourceAssociation.AssociatedItemId));
			Assert.AreSame(sourceAssociation, destinationAssociations[sourceAssociation.AssociatedItemId]);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociation"/> does nothing when there is
		/// already an association for the source association's item in the collection, and 
		/// that association is better than the source association.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationItemExistsAndOlderIsBetter()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			TestAssociation olderSourceAssociation;
			TestAssociation newerSourceAssociation;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			olderSourceAssociation = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2006, 01, 01),
				FinishDate = null,
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(olderSourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			newerSourceAssociation = new TestAssociation
			{
				AssociatedItemId = olderSourceAssociation.AssociatedItemId,
				StartDate = olderSourceAssociation.StartDate.AddDays(-1), // NOTE: The newer association starts one day before the older association, so the older association takes precedence.
				FinishDate = null,
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(newerSourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(1, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(olderSourceAssociation.AssociatedItemId));
			Assert.AreSame(olderSourceAssociation, destinationAssociations[olderSourceAssociation.AssociatedItemId]);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeAssociation"/> adds the source
		/// association to the destination associations collection when there is already an 
		/// association for the source association's item in the collection, but that
		/// association is worse than the source association.
		/// </summary>
		[TestMethod]
		public void TestMergeAssociationItemExistsButNewerIsBetter()
		{
			Dictionary<Guid, AssociationBase> destinationAssociations;
			TestAssociation olderSourceAssociation;
			TestAssociation newerSourceAssociation;

			destinationAssociations = new Dictionary<Guid, AssociationBase>();

			olderSourceAssociation = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2006, 01, 01),
				FinishDate = null,
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(olderSourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			newerSourceAssociation = new TestAssociation
			{
				AssociatedItemId = olderSourceAssociation.AssociatedItemId,
				StartDate = olderSourceAssociation.StartDate.AddDays(1), // NOTE: The newer association starts one day after the older association, so the newer association takes precedence.
				FinishDate = null,
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(newerSourceAssociation, destinationAssociations, new DateTime(2008, 06, 03));

			Assert.AreEqual(1, destinationAssociations.Count);
			Assert.IsTrue(destinationAssociations.ContainsKey(newerSourceAssociation.AssociatedItemId));
			Assert.AreSame(newerSourceAssociation, destinationAssociations[newerSourceAssociation.AssociatedItemId]);
		}

		#endregion // MergeAssociation

		#region MergeDisassociation

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeDisassociation"/> filters out 
		/// disassociations that have not started and does not remove their items from the 
		/// association collection.
		/// </summary>
		[TestMethod]
		public void TestMergeDisassociationDisassociationHasNotStarted()
		{
			Dictionary<Guid, AssociationBase> associations;
			TestAssociation association;
			TestDisassociation disassociation;

			associations = new Dictionary<Guid, AssociationBase>();
			association = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = new DateTime(2008, 06, 02),
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(association, associations, new DateTime(2008, 06, 01));
			Assert.AreEqual(1, associations.Count);

			disassociation = new TestDisassociation
			{
				DisassociatedItemId = association.AssociatedItemId,
				StartDate = new DateTime(2008, 06, 03),
				FinishDate = new DateTime(2008, 06, 04),
			};
			RelationsServiceTools.MergeDisassociation<TestDisassociation, AssociationBase>(disassociation, associations, new DateTime(2008, 06, 01));
			Assert.AreEqual(1, associations.Count);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeDisassociation"/> filters out 
		/// disassociations that have finished and does not remove their items from the 
		/// association collection.
		/// </summary>
		[TestMethod]
		public void TestMergeDisassociationDisassociationHasFinished()
		{
			Dictionary<Guid, AssociationBase> associations;
			TestAssociation association;
			TestDisassociation disassociation;

			associations = new Dictionary<Guid, AssociationBase>();
			association = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 03),
				FinishDate = new DateTime(2008, 06, 04),
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(association, associations, new DateTime(2008, 06, 03));
			Assert.AreEqual(1, associations.Count);

			disassociation = new TestDisassociation
			{
				DisassociatedItemId = association.AssociatedItemId,
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = new DateTime(2008, 06, 02),
			};
			RelationsServiceTools.MergeDisassociation<TestDisassociation, AssociationBase>(disassociation, associations, new DateTime(2008, 06, 03));
			Assert.AreEqual(1, associations.Count);
		}

		/// <summary>
		/// Ensures <see cref="RelationsServiceTools.MergeDisassociation"/> removes the 
		/// disassociation's item from the association collection.
		/// </summary>
		[TestMethod]
		public void TestMergeDisassociation()
		{
			Dictionary<Guid, AssociationBase> associations;
			TestAssociation association;
			TestDisassociation disassociation;

			associations = new Dictionary<Guid, AssociationBase>();
			association = new TestAssociation
			{
				AssociatedItemId = Guid.NewGuid(),
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = new DateTime(2008, 06, 02),
			};
			RelationsServiceTools.MergeAssociation<TestAssociation, AssociationBase>(association, associations, new DateTime(2008, 06, 01));
			Assert.AreEqual(1, associations.Count);

			disassociation = new TestDisassociation
			{
				DisassociatedItemId = association.AssociatedItemId,
				StartDate = new DateTime(2008, 06, 01),
				FinishDate = null,
			};
			RelationsServiceTools.MergeDisassociation<TestDisassociation, AssociationBase>(disassociation, associations, new DateTime(2008, 06, 01));
			Assert.AreEqual(0, associations.Count);
		}

		#endregion // MergeDisassociation

		#endregion // Methods

	}

}
